package pl.th.permission.plugin

import com.android.build.api.transform.*
import com.android.utils.FileUtils
import org.apache.commons.codec.digest.DigestUtils
import org.apache.commons.io.IOUtils
import org.gradle.api.Plugin
import org.gradle.api.Project
import org.objectweb.asm.ClassReader
import org.objectweb.asm.ClassWriter
import pl.th.permission.plugin.node.PermissionClassNode

import java.util.jar.JarEntry
import java.util.jar.JarFile
import java.util.jar.JarOutputStream
import java.util.zip.ZipEntry

public class PermissionZPlugin extends Transform implements Plugin<Project> {

    private static final String PLUGIN_TASK_NAME = "injectPermissionTask"

    private PermissionExtension permissionExt

    @Override
    void apply(Project project) {
        println("========================");
        println("hello buildSrc");
        println("========================");
        project.extensions.add("permission", PermissionExtension)
        permissionExt = project.getExtensions().getByType(PermissionExtension)
        if (!permissionExt.enable) return
        def appExtension = project.extensions.getByType(AppExtension)
        appExtension.registerTransform(this)
    }

    @Override
    String getName() {
        return PLUGIN_TASK_NAME
    }

    @Override
    Set<QualifiedContent.ContentType> getInputTypes() {
        return TransformManager.CONTENT_CLASS
    }

    @Override
    Set<? super QualifiedContent.Scope> getScopes() {
        return TransformManager.PROJECT_ONLY
    }

    @Override
    boolean isIncremental() {
        return false
    }

    @Override
    void transform(TransformInvocation invocation) throws TransformException, InterruptedException, IOException {
        final Context context = invocation.context
        final boolean isIncremental = invocation.isIncremental()
        final Collection<TransformInput> inputs = invocation.inputs
        final Collection<SecondaryInput> secondaryInputs = invocation.secondaryInputs
        final Collection<TransformInput> referencedInput = invocation.referencedInputs
        final TransformOutputProvider outputProvider = invocation.outputProvider
        assert outputProvider != null
        if (!isIncremental) {
            outputProvider.deleteAll()
        }
        def startTime = System.currentTimeMillis()
        inputs.each {TransformInput input ->
            input.directoryInputs.each {DirectoryInput directoryInput ->
                def directory = directoryInput.getFile()
                directory.eachFileRecurse(FileType.FILES, {File file ->
                    println("file.name = $file.name")
                    if (!isOwnClassFile(file)) return
                    ClassWriter classWriter = new ClassWriter(0)
                    ClassReader classReader = new ClassReader(file.bytes);
                    PermissionClassNode classNode = new PermissionClassNode(classWriter)
                    classReader.accept(classNode, ClassReader.EXPAND_FRAMES)
                    byte[] code = classWriter.toByteArray()
                    FileOutputStream fos;
                    try {
                        fos = new FileOutputStream(
                                file.parentFile.absolutePath + File.separator + file.name)
                        fos.write(code)
                    } finally {
                        if (fos != null) {
                            fos.close()
                        }
                    }
                })
                def dest = outputProvider.getContentLocation(directoryInput.name,
                        directoryInput.contentTypes, directoryInput.scopes,
                        Format.DIRECTORY)
                FileUtils.copyDirectory(directoryInput.file, dest)
            }
            input.jarInputs.each { JarInput jarInput ->
                def jarName = jarInput.name
                def md5Name = DigestUtils.md5Hex(jarInput.file.getAbsolutePath())
                def dest = outputProvider.getContentLocation(jarName + md5Name,
                        jarInput.contentTypes, jarInput.scopes, Format.JAR)
                FileUtils.copyFile(jarInput.file, dest)
            }
        }

        def cost = (System.currentTimeMillis() - startTime) / 1000
        println '--------------- LifecyclePlugin visit end --------------- '
        println "LifecyclePlugin cost ： $cost s"
    }

    private static boolean isOwnClassFile(File file) {
        def fileName = file.name
        return fileName.endsWith(".class") && !fileName.startsWith("R\$") &&
                fileName != "R.class" && fileName != "BuildConfig.class"
    }

    private static boolean isTargetClassFile(ClassReader classReader, String[] regex) {
        if (regex.length == 0) return true
        String className = classReader.className
        className = className.replace('/', '.')
        for (String ex : regex) {
            if (className ==~ ex) return true
        }
        return false
    }

    static void handleDirectoryInput(DirectoryInput directoryInput, TransformOutputProvider outputProvider) {
        //处理完输入文件之后，要把输出给下一个任务
        def dest = outputProvider.getContentLocation(directoryInput.name,
                directoryInput.contentTypes, directoryInput.scopes,
                Format.DIRECTORY)
        FileUtils.copyDirectory(directoryInput.file, dest)
    }

    static void handleJarInputs(JarInput jarInput, TransformOutputProvider outputProvider) {
        if (jarInput.file.getAbsolutePath().endsWith(".jar")) {
            //重名名输出文件,因为可能同名,会覆盖
            def jarName = jarInput.name
            def md5Name = DigestUtils.md5Hex(jarInput.file.getAbsolutePath())
            if (jarName.endsWith(".jar")) {
                jarName = jarName.substring(0, jarName.length() - 4)
            }
            JarFile jarFile = new JarFile(jarInput.file)
            Enumeration enumeration = jarFile.entries()
            File tmpFile = new File(jarInput.file.getParent() + File.separator + "classes_temp.jar")
            //避免上次的缓存被重复插入
            if (tmpFile.exists()) {
                tmpFile.delete()
            }
            JarOutputStream jarOutputStream = new JarOutputStream(new FileOutputStream(tmpFile))
            //用于保存
            while (enumeration.hasMoreElements()) {
                JarEntry jarEntry = (JarEntry) enumeration.nextElement()
                String entryName = jarEntry.getName()
                ZipEntry zipEntry = new ZipEntry(entryName)
                InputStream inputStream = jarFile.getInputStream(jarEntry)
                //插桩class
                if (entryName.endsWith(".class") && !entryName.startsWith("R\$")
                        && !"R.class".equals(entryName) && !"BuildConfig.class".equals(entryName)
                        && "android/support/v4/app/FragmentActivity.class".equals(entryName)) {
                    //class文件处理
                    println '----------- deal with "jar" class file <' + entryName + '> -----------'
//                    jarOutputStream.putNextEntry(zipEntry)
//                    ClassReader classReader = new ClassReader(IOUtils.toByteArray(inputStream))
//                    ClassWriter classWriter = new ClassWriter(classReader, ClassWriter.COMPUTE_MAXS)
//                    ClassVisitor cv = new LifecycleClassVisitor(classWriter)
//                    classReader.accept(cv, EXPAND_FRAMES)
//                    byte[] code = classWriter.toByteArray()
//                    jarOutputStream.write(code)
                } else {
                    jarOutputStream.putNextEntry(zipEntry)
                    jarOutputStream.write(IOUtils.toByteArray(inputStream))
                }
                jarOutputStream.closeEntry()
            }
            //结束
            jarOutputStream.close()
            jarFile.close()
            def dest = outputProvider.getContentLocation(jarName + md5Name,
                    jarInput.contentTypes, jarInput.scopes, Format.JAR)
            FileUtils.copyFile(tmpFile, dest)
            tmpFile.delete()
        }
    }
}
